﻿using DotNet.Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using WPFTemplateLib.Controls.WpfToast;

/*
 * 源码已托管：https://gitee.com/dlgcy/WPFTemplateLib
 */
namespace WPFTemplateLib.WpfHelpers
{
    /// <summary>
    /// WPF绑定基类;
    /// </summary>
    /// <example>
    /// <code>
    /// class Sample : BindableBase
    /// {
    ///     private List&lt;string&gt; _stuList;
    ///     public List&lt;string&gt; StuList
    ///     {
    ///         get => _stuList;
    ///         set => SetProperty(ref _stuList, value);
    ///     }
    /// }
    /// </code>
    /// </example>
    public abstract class BindableBase : INotifyPropertyChanged, INotifyPropertyChanging, INotifyDataErrorInfo
    {
        public Dispatcher Dispatcher => Application.Current?.Dispatcher;

        #region 构造函数

        protected BindableBase()
        {
            PropertyChanged += PropertyChangedHandle;
        }

        ~BindableBase()
        {
            PropertyChanged -= PropertyChangedHandle;
        }

        #endregion

        #region 绑定基础

        /// <summary>
        /// 属性变动处理方法（子类重写，依据属性名来判断并触发需要的操作）
        /// </summary>
        protected virtual void PropertyChangedHandle(object sender, PropertyChangedEventArgs e)
        {
            //子类重写
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public event PropertyChangingEventHandler PropertyChanging;

        /// <summary>
        /// 属性变动通知
        /// </summary>
        /// <param name="propertyName">属性名</param>
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// 属性即将变动通知
        /// </summary>
        /// <param name="propertyName">属性名</param>
        protected void OnPropertyChanging([CallerMemberName] string propertyName = null)
        {
            PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
        }

        protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if (Equals(storage, value)) return false;

            OnPropertyChanging(propertyName);
            storage = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        protected bool SetPropertyWithoutCompare<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            OnPropertyChanging(propertyName);
            storage = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        #endregion

        #region 验证

        /// <summary>
        /// 错误列表
        /// </summary>
        private Dictionary<string, List<string>> _Errors = new Dictionary<string, List<string>>();

        private void SetErrors(string propertyName, List<string> propertyErrors)
        {
            //clear any errors that already exist for this property.
            _Errors.Remove(propertyName);

            //Add the list collection for the specified property.
            _Errors.Add(propertyName, propertyErrors);

            //Raise the error-notification event.
            if (ErrorsChanged != null)
                ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
        }

        private void ClearErrors(string propertyName)
        {
            //Remove the error list for this property.
            _Errors.Remove(propertyName);

            //Raise the error-notification event.
            if (ErrorsChanged != null)
                ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
        }

        /// <summary>
        /// 是否包含错误
        /// </summary>
        /// <param name="propertyName">属性名</param>
        public bool IsContainErrors(string propertyName)
        {
            return (GetErrors(propertyName) as List<string>)?.Count > 0;
        }

        /// <summary>
        /// 是否包含错误
        /// </summary>
        /// <param name="propertyName">属性名列表</param>
        public bool IsContainErrors(List<string> propertyNameList)
        {
            return propertyNameList.Exists(x => IsContainErrors(x));
        }

        /// <summary>
        /// 获取给定属性列表的错误列表（参数传空则获取所有错误列表）
        /// </summary>
        /// <param name="propertyName">属性名列表</param>
        /// <returns>错误列表（List＜string＞）</returns>
        public List<string> GetErrors(List<string> propertyNameList)
        {
            if (!propertyNameList?.Any() ?? true)
            {
                return _Errors.Values.SelectMany(x => x).ToList();
            }
            else
            {
                List<string> errors = new List<string>();
                foreach (string propertyName in propertyNameList)
                {
                    if (_Errors.ContainsKey(propertyName))
                    {
                        errors.AddRange(_Errors[propertyName]);
                    }
                }

                return errors;
            }
        }

        #region INotifyDataErrorInfo

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        /// <summary>
        /// 获取属性错误列表（属性名传空则获取所有错误列表）
        /// </summary>
        /// <param name="propertyName">属性名</param>
        /// <returns>错误列表(List＜List＜string＞＞)</returns>
        public IEnumerable GetErrors(string propertyName)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                //Provide all the error collections.
                return _Errors.Values;
            }
            else
            {
                //Provice the error collection for the requested property (if it has errors).
                if (_Errors.ContainsKey(propertyName))
                {
                    return _Errors[propertyName];
                }
                else
                {
                    return null;
                }
            }
        }

        /// <summary>
        /// 整个类是否存在错误
        /// </summary>
        public bool HasErrors => _Errors.Count > 0;

        #endregion

        #region 实用方法(供参考)

        /// <summary>
        /// 验证是否为空
        /// </summary>
        /// <returns>true-不为空，false-为空</returns>
        public virtual bool ValidateBlank(object value, string errMsg = "", [CallerMemberName] string propertyName = null)
        {
            bool valid = !string.IsNullOrWhiteSpace(value + "");
            if (!valid) //为空；
            {
                if (string.IsNullOrWhiteSpace(errMsg))
                {
                    errMsg = $"[{propertyName}] can't be blank";
                }

                SetErrors(propertyName, new List<string>() { errMsg });
            }
            else
            {
                ClearErrors(propertyName);
            }

            return valid;
        }

        #endregion

        #endregion

        #region 程序运行消息输出

        private string _Info;
        /// <summary>
        /// 信息窗内容;
        /// </summary>
        public string Info
        {
            get => _Info;
            set => SetPropertyWithoutCompare(ref _Info, value);
        }

        /// <summary>
        /// 输出信息方法（界面需绑定 Info）
        /// </summary>
        /// <param name="info">信息内容</param>
        /// <param name="isAddBlankLine">是否空行</param>
        public void ShowInfo(string info, bool isAddBlankLine = false)
        {
            string tail = Environment.NewLine.Repeat(isAddBlankLine ? 2 : 1);
            Info += $"[{DateTime.Now:HH:mm:ss.ffff}] {info}{tail}";
        }

        /// <summary>
        /// 使用信息区用户控件时使用的输出信息方法（界面需使用 UC_InfoRegion 并将 InfoText 绑定 Info）
        /// </summary>
        /// <param name="info">信息内容</param>
        /// <param name="isAddBlankLine">是否空行</param>
        public void ShowInfoUc(string info, bool isAddBlankLine = false)
        {
            string tail = Environment.NewLine.Repeat(isAddBlankLine ? 1 : 0);
            Dispatcher.Invoke(() =>
            {
                Info = $"{info}{tail}";
                OnPropertyChanged(nameof(Info)); //如果值相同，set 方法不会进入，所以此处手动调用变动通知;
            });
        }

        #endregion

        #region 气泡弹窗命令

        private RelayCommand<List<object>> _ToastToWindowCmd;
        /// <summary>
        /// 气泡弹窗(到窗体)命令
        /// </summary>
        /// <example>
        /// <code>
        /// //Xaml中调用：
        /// &lt;Button Content="测试通过命令调用（窗体中间）" Command="{Binding ToastToWindowCmd}">
        ///      &lt;Button.CommandParameter>
        ///          &lt;MultiBinding Converter="{StaticResource MultiToListObjectConverter}">
        ///              &lt;Binding Path="Content" RelativeSource="{RelativeSource Self}"/>
        ///              &lt;Binding RelativeSource="{RelativeSource AncestorType=Window}"/>
        ///          &lt;/MultiBinding>
        ///      &lt;/Button.CommandParameter>
        ///  &lt;/Button>
        /// </code>
        /// <code>
        /// //代码中调用：
        /// ToastToWindowCmd.Execute(new List&lt;object&gt; { "请先选择一项" });
        /// </code>
        /// </example>
        public RelayCommand<List<object>> ToastToWindowCmd => _ToastToWindowCmd ??= new RelayCommand<List<object>>(paras =>
        {
            Task.Run(delegate
            {
                string msg = string.Empty;
                if (paras.Any())
                {
                    msg = paras[0] + "";
                }

                Window win = null;
                if (paras.Count >= 2)
                {
                    win = paras[1] as Window;
                }

                Dispatcher.Invoke(delegate
                {
                    Toast.Show(win, msg, new ToastOptions()
                    {
                        Icon = ToastIcons.Information,
                        Location = ToastLocation.OwnerCenter,
                        Time = 5000,
                    });
                });
            });
        });

        private RelayCommand<string> _ToastToScreenCmd;
        /// <summary>
        /// 气泡弹窗(到屏幕)命令
        /// </summary>
        /// <example>
        /// <code>
        /// //Xaml中调用：
        /// &lt;Button Content="测试通过命令调用（屏幕中间）" Command="{Binding ToastToScreenCmd}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"/&gt;
        /// </code>
        /// <code>
        /// //代码中调用：
        /// ToastToScreenCmd.Execute("请先选择一项");
        /// </code>
        /// </example>
        public RelayCommand<string> ToastToScreenCmd => _ToastToScreenCmd ??= new RelayCommand<string>(msg =>
        {
            if (string.IsNullOrWhiteSpace(msg))
            {
                return;
            }

            Task.Run(delegate
            {
                Dispatcher.Invoke(delegate
                {
                    Toast.Show(msg, new ToastOptions()
                    {
                        Icon = ToastIcons.Information,
                        Location = ToastLocation.ScreenCenter,
                        Time = 5000,
                    });
                });
            });
        });

        #endregion
    }
}
